1 module hip.tween; 2 3 import core.stdc.math:cos, sin, pow, sqrt; 4 private enum PI = 3.141592653589793; 5 public import hip.timer; 6 7 private enum c1 = 1.70158f; 8 private enum c2 = c1 * 1.525; 9 private enum c3 = c1+1; 10 private enum c4 = (2.0f * PI)/3.0f; 11 private enum c5 = (2 * PI) / 4.5f; 12 private enum n1 = 7.5625f; 13 private enum d1 = 2.75f; 14 15 //TODO: Change (x) to (float x) for not generating templates needlessly 16 /** 17 * Credits to https://easings.net as I don't understand most of those functions 18 */ 19 enum HipEasing : float function(float x) 20 { 21 identity = (x) => x, 22 easeInSine = (x) => 1 - cos((x*PI)/2), 23 easeOutSine = (x) => sin((x*PI)/2), 24 easeInOutSine = (x) => -(cos(PI*x) - 1)/2, 25 easeInQuad = (x) => x*x, 26 easeOutQuad = (x) => 1 - (1-x) * (1-x), 27 easeInOutQuad = (x) => x < 0.5f ? 2 *x*x : 1 - pow(-2 * x + 2, 2)/2, 28 easeInCubic = (x) => x*x*x, 29 easeOutCubic = (x) => 1 - pow(1-x, 3), 30 easeInOutCubic = (x) => x < 0.5 ? 4 * x * x * x : 1 - pow(-2 * x + 2, 3)/2, 31 easeInQuart = (x) => x*x*x*x, 32 easeOutQuart = (x) => 1 - pow(1-x, 4), 33 easeInOutQuart = (x) => x < 0.5 ? 8 * x * x * x * x : 1 - pow(-2 * x + 2, 4)/2, 34 easeInQuint = (x) => x*x*x*x*x, 35 easeOutQuint = (x) => 1 - pow(1-x, 5), 36 easeInOutQuint = (x) => x < 0.5 ? 16 * x * x * x * x * x : 1 - pow(-2 * x + 2, 5)/2, 37 easeInExpo = (x) => x == 0 ? 0 : pow(2, 10 * x- 10), 38 easeOutExpo = (x) => x == 1 ? 1 : 1 - pow(2, -10 * x), 39 easeInOutExpo = (x) => x == 0 ? 0 40 : x == 1 ? 1 41 : x < 0.5 ? pow(2, 20 * x - 10)/2 42 : (2 - pow(2, -20 * x + 10))/2, 43 easeInCirc = (x) => 1 - sqrt(1 - pow(x, 2)), 44 easeOutCirc = (x) => sqrt(1 - pow(x - 1, 2)), 45 easeInOutCirc = (x) => x < 0.5 ? (1 - sqrt(1 - pow(2 * x, 2)))/2 46 : (sqrt(1 - pow(-2 * x + 2, 2)) + 1) / 2, 47 easeInBack = (x) => c3 * x * x * x - c1 * x * x, 48 easeOutBack = (x) => 1 + c3 * pow(x - 1, 3) + c1 * pow(x-1, 2), 49 easeInOutBack = (x) => x < 0.5 ? (pow(2*x, 2) * ((c2+1) * 2 * x - c2))/2 50 : (pow(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2)/2, 51 easeInElastic = (x) => x == 0 ? 0 52 : x == 1 ? 1 53 : -pow(2, 10 * x - 10) * sin((x * 10 - 10.75f) * c4), 54 easeOutElastic = (x) => x == 0 ? 0 55 : x == 1 ? 1 56 : pow(2, -10 * x) * sin((x * 10 - 0.75f) * c4) + 1, 57 easeInOutElastic= (x) => x == 0 ? 0 : x == 1 ? 1 : x < 0.5 58 ? -(pow(2, 20 * x - 10) * sin((20 * x - 11.125f) * c5))/2 59 : (pow(2, -20 * x + 10) * sin((20 * x - 11.125f) * c5))/2 + 1, 60 easeInBounce = (x) => 1 - HipEasing.easeOutBounce(1 - x), 61 easeOutBounce = function float(x) 62 { 63 if(x < 1.0f / d1) 64 return n1 * x * x; 65 else if(x < 2.0f / d1) 66 return n1 * (x-= 1.5f / d1) * x + 0.75; 67 else if(x < 2.5f / d1) 68 return n1 * (x-= 2.25f / d1) * x + 0.9375f; 69 else 70 return n1 * (x-= 2.625f / d1) * x + 0.984375f; 71 }, 72 easeInOutBounce = (x) => x < 0.5 ? (1 - easeOutBounce(1 - 2 * x))/2 73 : (1+ easeOutBounce(2 * x - 1))/2 74 } 75 76 77 class HipTween : HipTimer, IHipFiniteTask 78 { 79 HipEasing easing = null; 80 protected void[] savedData = null; 81 82 protected void delegate() onPlay; 83 protected void delegate()[] onFinish; 84 85 this(float durationSeconds, bool loops = false) 86 { 87 super("Tween", durationSeconds, HipTimerType.progressive, loops); 88 this.easing = null; 89 } 90 HipTween setEasing(HipEasing easing){this.easing = easing; return this;} 91 void setProperties(string name, float durationSeconds, bool loops = false) 92 { 93 super.setProperties(name, durationSeconds, HipTimerType.progressive, loops); 94 } 95 override HipTween play() 96 { 97 super.play(); 98 if(onPlay != null) 99 onPlay(); 100 return this; 101 } 102 override void stop() 103 { 104 super.stop(); 105 foreach(finish; onFinish) 106 finish(); 107 } 108 109 protected void allocSaveData(size_t size) 110 { 111 if(savedData !is null) 112 destroy(savedData); 113 savedData = new void[](size); 114 } 115 116 private static void checkSizes(size_t lengthValues, size_t lengthTarget) 117 { 118 assert(lengthTarget >= 1, "Target values must have 1 or more values"); 119 if(lengthTarget != 1) 120 assert(lengthValues == lengthTarget, 121 "Must have the same number of pointers to targets if targetValues is greater than 1" 122 ); 123 } 124 125 /** 126 * This version is more lightweight compiler wise as it is not templated 127 */ 128 static HipTween to (float durationSeconds, float*[] valuesRef, in float[] targetValues ...) 129 { 130 checkSizes(valuesRef.length, targetValues.length); 131 HipTween t = new HipTween(durationSeconds, false); 132 float[] v2 = targetValues.dup; 133 t.allocSaveData(valuesRef.length * float.sizeof); 134 135 t.onPlay = () 136 { 137 float[] savedDataConv = cast(float[])t.savedData; 138 foreach(i, v; valuesRef) 139 savedDataConv[i] = *v; 140 141 t.addHandler((float prog, uint loops) 142 { 143 float multiplier = prog; 144 if(t.easing != null) 145 multiplier = t.easing(multiplier); 146 float initialValue; 147 float newValue; 148 149 foreach(i, value; valuesRef) 150 { 151 initialValue = savedDataConv[i]; 152 if(v2.length > 1) 153 newValue = ((1-multiplier)*initialValue + (v2[i] * multiplier)); 154 else 155 newValue = ((1-multiplier)*initialValue + (v2[0] * multiplier)); 156 *value = newValue; 157 } 158 }); 159 }; 160 return t; 161 } 162 163 static HipTween to(string[] Props, T, V)(float durationSeconds, T target, V[] values...) 164 { 165 checkSizes(Props.length, values.length); 166 HipTween t = new HipTween(durationSeconds, false); 167 t.allocSaveData(Props.length * V.sizeof); 168 169 V[] v2 = values.dup; 170 t.onPlay = () 171 { 172 V[] savedDataConv = cast(V[])t.savedData; 173 static foreach(i, p; Props) 174 { 175 savedDataConv[i] = cast(V)__traits(getMember, target, p); 176 } 177 178 179 t.addHandler((float prog, uint loops) 180 { 181 float multiplier = prog; 182 if(t.easing != null) 183 multiplier = t.easing(multiplier); 184 V initialValue; 185 V newValue; 186 static foreach(i, p; Props) 187 { 188 initialValue = savedDataConv[i]; 189 if(v2.length > 1) 190 newValue = cast(V)((1-multiplier)*initialValue + (v2[i] * multiplier)); 191 else 192 newValue = cast(V)((1-multiplier)*initialValue + (v2[0] * multiplier)); 193 __traits(getMember, target, p) = newValue; 194 } 195 }); 196 }; 197 return t; 198 } 199 200 static HipTween by(float durationSeconds, float*[] valuesRef, float[] targetValues) 201 { 202 checkSizes(valuesRef.length, targetValues.length); 203 HipTween t = new HipTween(durationSeconds, false); 204 t.allocSaveData(float.sizeof * valuesRef.length); 205 float[] v2 = targetValues.dup; 206 207 t.onPlay = () 208 { 209 t.addHandler((float prog, uint loops) 210 { 211 float[] savedDataConv = cast(float[])t.savedData; 212 float multiplier = prog; 213 if(t.easing != null) 214 multiplier = t.easing(multiplier); 215 float temp; 216 float temp2; 217 foreach(i, valueRef; valuesRef) 218 { 219 temp = savedDataConv[i]; 220 if(v2.length > 1) 221 temp2 = (v2[i] * multiplier); 222 else 223 temp2 = (v2[0] * multiplier); 224 225 *valueRef+= -temp + temp2; 226 //Copy the new values for being subtracted next frame 227 savedDataConv[i] = temp2; 228 229 } 230 }); 231 }; 232 233 return t; 234 } 235 236 static HipTween by(string[] Props, T, V)(float durationSeconds, T target, V[] values...) 237 { 238 checkSizes(Props.length, values.length); 239 HipTween t = new HipTween(durationSeconds, false); 240 t.allocSaveData(Props.length * V.sizeof); 241 V[] v2 = values.dup; 242 243 t.onPlay = () 244 { 245 t.addHandler((float prog, uint loops) 246 { 247 V[] savedDataConv = cast(V[])t.savedData; 248 float multiplier = prog; 249 if(t.easing != null) 250 multiplier = t.easing(multiplier); 251 V temp; 252 V temp2; 253 static foreach(i, p; Props) 254 { 255 temp = savedDataConv[i]; 256 if(v2.length > 1) 257 temp2 = cast(V)(v2[i] * multiplier); 258 else 259 temp2 = cast(V)(v2[0] * multiplier); 260 261 __traits(getMember, target, p)+= -temp + temp2; 262 //Copy the new values for being subtracted next frame 263 savedDataConv[i] = temp2; 264 } 265 }); 266 }; 267 268 return t; 269 } 270 271 HipTween addOnFinish(void delegate() onFinish) 272 { 273 this.onFinish~= onFinish; 274 return this; 275 } 276 ~this(){destroy(savedData);} 277 278 }